﻿#include "stdafx.h"
#if _QG_USE_D11
#include "qg_d11_dev.h"
#include "qg_d11_mtrl.h"
#include "qg_d11_draw.h"
#include "qg_d11_cnv.h"
#include "qg_trace.h"

// 그리기 바인드, 오류 검사 안함
bool D11Device::_BindDraw(bool bindindex) throw()
{
	ksize_t i;

	// 꼭지점
	for (i = 0; i < QGSGVERT_MAX_VALUE; i++)
	{
		if (_sprn->bd_vb[i])
			break;
	}

	if (i < QGSGVERT_MAX_VALUE)
	{
		ID3D11Buffer* vbs[QGSGVERT_MAX_VALUE];
		UINT strides[QGSGVERT_MAX_VALUE];
		UINT offsets[QGSGVERT_MAX_VALUE];

		for (i = 0; i < QGSGVERT_MAX_VALUE; i++)
		{
			if (!_sprn->bd_vb[i])
			{
				if (!_sprn->w_vb[i])
				{
					vbs[i] = NULL;
					strides[i] = 0;
					offsets[i] = 0;
				}
				else
				{
					vbs[i] = (ID3D11Buffer*)_sprn->w_vb[i]->GetDescription();
					strides[i] = (UINT)_sprn->w_vb[i]->GetStride();
					offsets[i] = 0;
				}
			}
			else
			{
				_sprn->bd_vb[i] = false;

				if (!_sprn->w_vb[i])
				{
					vbs[i] = NULL;
					strides[i] = 0;
					offsets[i] = 0;
				}
				else
				{
					vbs[i] = (ID3D11Buffer*)_sprn->w_vb[i]->GetDescription();
					strides[i] = (UINT)_sprn->w_vb[i]->GetStride();
					offsets[i] = 0;

					// 레이아웃과 비교
					if (strides[i] != (UINT)_sprn->w_lo->GetStageStride((kint)i))
					{
						// 폭이 틀리면 경고!!!
						kobj_tracep(KLV_WARNING, KERR_STRIDE);
					}
				}
			}
		}

		_context->IASetVertexBuffers(0, QGSGVERT_MAX_VALUE, vbs, strides, offsets);
	}

	// 인덱스
	if (bindindex && _sprn->bd_ib)
	{
		_sprn->bd_ib = false;

		DXGI_FORMAT fmt = _sprn->w_ib->GetStride() == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT;
		ID3D11Buffer* ib = (ID3D11Buffer*)_sprn->w_ib->GetDescription();
		_context->IASetIndexBuffer(ib, fmt, 0);

		// [2014-1-7 KSH] 참고
		// w_ioff은 DrawIndexed에서 사용하고, 여기의 옵셋은 0으로 한다.
		// 만약 나중에 여기 옵셋이 메모리 용량과 속도에 이득이 있다면, 
		// 이쪽에서 설정하도록 바꾸어야 한다
		// 이것은 인덱스 설정에 오프셋이 없는 DX9과 OpenGL 호환을 위해서 이렇게 한거임
	}

	// 레이아웃
	if (_sprn->bd_lo)
	{
		_sprn->bd_lo = false;

		ID3D11InputLayout* il = (ID3D11InputLayout*)_sprn->w_lo->GetDescription();

		if (!il)
		{
			// 아직 만들어지지 않았음. 만들자
			// -> 즉 세이더와 묶이지 않았다는 뜻!
			D11Layout* dl = (D11Layout*)_sprn->w_lo;

			if (!dl->_BakeShader(_sprn->m_shd, &il, this))
			{
				// 메시지는 BakeShader안에서 처리함
				return false;
			}
		}

		_context->IASetInputLayout(il);
	}

	// 토폴로지
	if (_sprn->bd_tpg)
	{
		_sprn->bd_tpg = false;

		_context->IASetPrimitiveTopology(D11Convert::Topology(_sprn->w_tpg));
	}

	//
	return true;
}

// 버퍼 만들기
qgBuffer* D11Device::_RoamImplBuffer(const qgArprBuffer& ap)
{
	if (ap.type == QGBUFFER_INDEX || ap.type == QGBUFFER_VERTEX)
	{
		D11Buffer* buf = kobj_new(D11Buffer);

		if (!buf->_Create(ap, this))
		{
			buf->Unload();
			buf = NULL;
		}

		return buf;
	}
	else
	{
		// 인덱스도 아니고
		// 꼭지점도 아니면
		// 실패지 뭐...
		return NULL;
	}
}

// 레이아웃 만들기
qgLayout* D11Device::_RoamImplLayout(qgLayoutData* data)
{
	D11Layout* lo = kobj_new(D11Layout);

	if (!lo->_Create(data))
	{
		lo->Unload();
		lo = NULL;
	}

	return lo;
}


//////////////////////////////////////////////////////////////////////////
// 버퍼
KIMPL_SUBCLASS(D11Buffer, "D11Buffer");

//
D11Buffer::D11Buffer() throw()
{
}

//
D11Buffer::~D11Buffer() throw()
{
	if (_node_desc)
	{
		if (_lock)
			D11Buffer::LeaveMap();

		ID3D11Buffer* buf = (ID3D11Buffer*)_node_desc;
		buf->Release();
	}
}

//
bool D11Buffer::_Create(const qgArprBuffer& ap, D11Device* dev)
{
	ID3D11Buffer* buf;

	D3D11_SUBRESOURCE_DATA sd, *psd;

	//
	_desc.ByteWidth = ap.count * ap.stride;

	if (K_OFLAG(ap.flags, QGRESF_INIT))
	{
		_desc.Usage = D3D11_USAGE_IMMUTABLE;
		_desc.BindFlags = ap.type == QGBUFFER_VERTEX ? D3D11_BIND_VERTEX_BUFFER : D3D11_BIND_INDEX_BUFFER;
		_desc.CPUAccessFlags = 0;
		psd = &sd;
		sd.pSysMem = ap.data;
		sd.SysMemPitch = 0;
		sd.SysMemSlicePitch = 0;
	}
	else if (K_OFLAG(ap.flags, QGRESF_DYNAMIC))
	{
		_desc.Usage = D3D11_USAGE_DYNAMIC;
		_desc.BindFlags = ap.type == QGBUFFER_VERTEX ? D3D11_BIND_VERTEX_BUFFER : D3D11_BIND_INDEX_BUFFER;
		_desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
		psd = NULL;
	}
	else if (K_OFLAG(ap.flags, QGRESF_LOCAL))
	{
		// 스태이깅 = 예전의 SYSMEM. 
		// : 특정 용도로 바인딩 할 수가 없다.
		// : CPU 플래그가 반드시 지정되어야 한다
		_desc.Usage = D3D11_USAGE_STAGING;
		_desc.BindFlags = 0;
		_desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE | D3D11_CPU_ACCESS_READ;
		psd = NULL;
	}
	else
	{
		_desc.Usage = D3D11_USAGE_DEFAULT;
		_desc.BindFlags = ap.type == QGBUFFER_VERTEX ? D3D11_BIND_VERTEX_BUFFER : D3D11_BIND_INDEX_BUFFER;
		_desc.CPUAccessFlags = 0;
		psd = NULL;
	}

	if (K_OFLAG(ap.flags, QGBUFF_STREAM) && !psd)
		_desc.BindFlags |= D3D11_BIND_STREAM_OUTPUT;

	if (FAILED(dev->GetDevice()->CreateBuffer(&_desc, psd, &buf)))
	{
		kobj_tracep(KLV_ERROR, KERR_FAIL_CREATE);
		return false;
	}

	//
	_buftype = ap.type;
	_flags = ap.flags;
	_count = ap.count;
	_stride = ap.stride;

	_node_desc = (kuintptr)buf;

	// 데이터가 있고 IMMUTABLE이 아니면 써주자
	if (ap.data && !psd)
	{
		kpointer ptr = D11Buffer::EnterMap(QGENTF_NONE);
		if (ptr)
		{
			memcpy(ptr, ap.data, _desc.ByteWidth);
			D11Buffer::LeaveMap();
		}
	}

	return true;
}

//
kpointer D11Buffer::EnterMap(kint flags)
{
	// 잠겨 있으면 실패
	if (_lock)
	{
		kobj_tracep(KLV_ERROR, KERR_MAPPING);
		return NULL;
	}

	// 초기 데이터가 있으면 잠글 수 없음
	if (_desc.Usage == D3D11_USAGE_IMMUTABLE)
	{
		kobj_tracep(KLV_ERROR, KERR_MAPPING);
		return NULL;
	}

	//
	D11Device* dev = (D11Device*)_dev;
	D3D11_MAPPED_SUBRESOURCE ms;

	//
	if (K_OFLAG(_flags, QGRESF_LOCAL) ||
		((K_OFLAG(_flags, QGRESF_DYNAMIC) && flags == QGENTF_NONE)))
	{
		// 스테이깅이거나 다이나믹이면서 플래그가 없으면 바로 매핑
		D3D11_MAP map =
			K_OFLAG(flags, QGENTF_READ) ?
			K_OFLAG(flags, QGENTF_WRITE) ?
D3D11_MAP_READ_WRITE : D3D11_MAP_READ : D3D11_MAP_WRITE_DISCARD;

		ID3D11Buffer* buf = (ID3D11Buffer*)_node_desc;

		if (FAILED(dev->GetContext()->Map(buf, 0, map, 0, &ms)))
		{
			kobj_tracep(KLV_ERROR, KERR_MAPPING);
			return NULL;
		}
	}
	else
	{
		// 스태깅 임시 버퍼를 만들어서 반환...
		// 왜냐하면 D11은 스태깅 이외에는 거의 매핑할 수 없어 리소스 자체 복제를 해야함

		// 임시 버퍼 만들고
		D3D11_BUFFER_DESC bd;
		bd.ByteWidth = _desc.ByteWidth;
		bd.Usage = D3D11_USAGE_STAGING;
		bd.BindFlags = 0;
		bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE | D3D11_CPU_ACCESS_READ;
		bd.MiscFlags = 0;
		bd.StructureByteStride = 0;

		if (FAILED(dev->GetDevice()->CreateBuffer(&bd, NULL, &_tmpbuf)))
		{
			kobj_tracep(KLV_ERROR, KERR_FAIL_CREATE);
			return NULL;
		}

		// 읽기가 가능하면... 읽게 해줘야지... ㅠ_ㅜ
		if (K_OFLAG(flags, QGENTF_READ))
		{
			ID3D11Buffer* buf = (ID3D11Buffer*)_node_desc;
			dev->GetContext()->CopyResource(_tmpbuf, buf);
		}

		// 임시 스태깅으로 매핑
		if (FAILED(dev->GetContext()->Map(_tmpbuf, 0, D3D11_MAP_READ_WRITE, 0, &ms)))
		{
			kobj_tracep(KLV_ERROR, KERR_MAPPING);
			return NULL;
		}

		// 까먹지 말고 세팅할 것
		_upload = flags != QGENTF_READ;	// 읽기 전용이 아니면 나중에 반드시 업로드해야함
	}

	//
	_lock = ms.pData;

	return ms.pData;
}

//
bool D11Buffer::LeaveMap()
{
	if (!_lock)
	{
		// 잠겨있지 않음
		kobj_tracep(KLV_ERROR, KERR_MAPPING);
		return false;
	}

	D11Device* dev = (D11Device*)_dev;
	ID3D11DeviceContext* context = dev->GetContext();

	ID3D11Buffer* buf = (ID3D11Buffer*)_node_desc;

	if (_tmpbuf)
	{
		// 임시 버퍼를 통한 처리
		context->Unmap(_tmpbuf, 0);

		if (_upload)
			context->CopyResource(buf, _tmpbuf);

		_tmpbuf->Release();
		_tmpbuf = NULL;;
	}
	else
	{
		// 바로 해제
		context->Unmap(buf, 0);
	}

	//
	_lock = NULL;

	return true;
}


//////////////////////////////////////////////////////////////////////////
// 레이아웃
KIMPL_SUBCLASS(D11Layout, "D11Layout");

//
D11Layout::D11Layout() throw()
{
}

//
D11Layout::~D11Layout() throw()
{
	if (_node_desc)
	{
		ID3D11InputLayout* il = (ID3D11InputLayout*)_node_desc;
		il->Release();
	}
}

//
LPCSTR D11Layout::_ConvSemanticName(qgDloUsage usage)
{
	static const char* sNames[] =
	{
		"POSITION",
		"COLOR",
		"BLENDWEIGHT",
		"BLENDINDICES",
		"NORMAL",
		"TEXCOORD",
		"TANGENT",
		"BINORMAL",
		"POSITION",
	};

	static_assert(K_COUNTOF(sNames) >= QGDLOU_MAX_VALUE, "invalid layout type count");

	k_assert(usage >= 0 && usage < K_COUNTOF(sNames), "range error");

	return sNames[usage];
}

//
DXGI_FORMAT D11Layout::_ConvFormat(qgDloType type)
{
	static DXGI_FORMAT sFmt[] =
	{
		DXGI_FORMAT_R32_FLOAT,
		DXGI_FORMAT_R32G32_FLOAT,
		DXGI_FORMAT_R32G32B32_FLOAT,
		DXGI_FORMAT_R32G32B32A32_FLOAT,
		DXGI_FORMAT_R32_UINT,
		DXGI_FORMAT_R32G32_UINT,
		DXGI_FORMAT_R32G32B32_UINT,
		DXGI_FORMAT_R32G32B32A32_UINT,
		DXGI_FORMAT_R16G16_FLOAT,
		DXGI_FORMAT_R16G16B16A16_FLOAT,
		DXGI_FORMAT_R8G8_UINT,
		DXGI_FORMAT_R8G8B8A8_UINT,
		DXGI_FORMAT_R16G16_UINT,
		DXGI_FORMAT_R16G16B16A16_UINT,
		DXGI_FORMAT_R8G8B8A8_UNORM,
	};

	k_assert(type >= 0 && type < K_COUNTOF(sFmt), "range error");

	return sFmt[type];
}

//
bool D11Layout::_Create(qgLayoutData* data)
{
	// 여기서는 목록만 만든다.
	// 왜냐하면 실제 InputLayout은 꼭지점 세이더의 코드데이터가 있어야함
	// 이유인즉슨 맞는지 확인(validation) 과정을 사전에 한번에 처리한다나 뭐래나...

	// 루프
	kint i, cnt = data->elms.Count();
	kushort size, offset[QGSGVERT_MAX_VALUE] = {0, 0, 0, 0};

	_descs.Resize(cnt);

	for (i = 0; i < cnt; i++)
	{
		D3D11_INPUT_ELEMENT_DESC& d = _descs[i];
		qgLayoutElement& e = data->elms[i];

		//
		size = (kushort)qgResSupp::LayoutTypeToSize(e.type);

		// 추가 데이터 처리
		e.offset = offset[e.slot];
		e.size = size;

		// 기본
		d.SemanticName = _ConvSemanticName(e.usage);
		d.SemanticIndex = e.index;
		d.Format = _ConvFormat(e.type);
		d.InputSlot = e.slot;
		d.AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;

		// [2014-1-1 KSH]
		// 아래 두개는 꼭지점 데이터냐, 인스턴스 데이터냐를 처리하는 건데
		// 입력 플래그를 받아 처리하도록 구조를 개선해야 함. 현재는 꼭지점 데이터만 처리
		// e.insts값에 인스턴스인지 아닌지 판단하는 방식을 고려할 것
		d.InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
		d.InstanceDataStepRate = 0;

		// 오프셋 전진
		offset[e.slot] += size;
	}

	// 0번의 AlignedByteOffset은 0으로 해야함
	_descs[0].AlignedByteOffset = 0;

	// 키
	data->elms.Swap(_data.elms);

	// [2014-3-2 KIM] 값 
	for (_stride = i = 0; i < QGSGVERT_MAX_VALUE; i++)
	{
		_stride += offset[i];
		_stage[i] = offset[i];
	}

	return true;
}

//
bool D11Layout::_BakeShader(qgShader* shd, ID3D11InputLayout** ret, D11Device* dev)
{
	// 실제 만든다...

	// 정점 세이더 확인
	D11Shader* ds = (D11Shader*)shd;
	tpl::Array<kbyte>& keep = ds->_dvs->keep;

	if (keep.IsEmpty())
	{
		// 정점 세이더 코드 데이터가 없다!
		kobj_tracep(KLV_ERROR, KERR_NOTARGET);
		return false;
	}

	//
	ID3D11InputLayout* il;

	HRESULT hr = dev->GetDevice()->CreateInputLayout(
		_descs.Data(), _descs.Count(), keep.Data(), keep.Count(), &il);

	if (FAILED(hr))
	{
		// 헐... 못 만듦
		kobj_tracep(KLV_ERROR, KERR_FAIL_CREATE);
		return false;
	}

	// 레이아웃, 세이더 둘다 넣어줌
	_node_desc = (kuintptr)il;

	if (!ds->_node_desc)
	{
		ds->_node_desc = (kuintptr)il;
		il->AddRef();
	}

	if (ret)
		*ret = il;

	return true;
}

#endif	// _QG_USE_D11
